/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "tansu_status.h"
#include "tansu_socket.h"

tansu_status_t tansu_socket_error(int rc);

tansu_status_t tansu_socket_close(int fd)
{
#ifndef WIN32
    shutdown(fd, SHUT_RDWR);
#endif /* WIN32 */
    while (TANSU_SOCKET_CLOSE(fd) < 0) {
        int rc = TANSU_GET_SOCKET_ERROR();
        if (rc == TANSU_SOCKET_EINTR)
            continue;
        TLOG_ERROR(TLOG_SOCKET, "close() failed %d\n", rc);
        return tansu_socket_error(rc);
    }

    return TANSU_SUCCESS;
}

#ifdef WIN32
tansu_status_t tansu_socket_error(int rc)
{
    switch (rc) {
    case WSANOTINITIALISED:
    case WSAENETDOWN:
        return TANSU_INVALID_STATE;
    case WSAENOTSOCK:
        return TANSU_NOT_FOUND;
    case WSAEINPROGRESS:
    case WSAEWOULDBLOCK:
        return TANSU_INPROGRESS;
    default:
        return TANSU_ERR_UNKNOWN;
    }
}
#else /* WIN32 */
tansu_status_t tansu_socket_error(int rc)
{
    switch (rc) {
    case EBADF:
        return TANSU_NOT_FOUND;
    case EIO:
    default:
        return TANSU_ERR_UNKNOWN;
    }
}
#endif /* WIN32 */


#if HAVE_IFADDRS_H
#else /* HAVE_IFADDRS_H */
static void freeifaddr(struct ifaddrs *ifp)
{
    if (ifp) {
        free(ifp->ifa_name);
        free(ifp);
    }
}

static struct ifaddrs *
getifaddr(int family, const char *if_name)
{
    struct ifaddrs *ifp = NULL;
    int sock;
    struct ifreq ifr;

    if ((sock = socket(family, SOCK_DGRAM, 0)) == -1)
        return NULL;

    if ((ifp = calloc(1, sizeof(struct ifaddrs))) == NULL)
        return NULL;

    if ((ifp->ifa_name = strdup(if_name)) == NULL)
        goto out;

    ifp->ifa_addr = &ifp->sa_addr;
    ifp->ifa_netmask = &ifp->sa_netmask;

    /* set interface name */
    memset(&ifr, 0, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, if_name, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = '\0';

    /* flags */
    if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)
        goto out;
    ifp->ifa_flags = ifr.ifr_flags;

    /* ip adrress */
    if (ioctl(sock, SIOCGIFADDR, &ifr) != -1)
        memcpy(ifp->ifa_addr, &ifr.ifr_addr, sizeof(struct sockaddr));

    /* netmask */
    if (ioctl(sock, SIOCGIFNETMASK, &ifr) != -1)
        memcpy(ifp->ifa_netmask, &ifr.ifr_netmask, sizeof(struct sockaddr));

    tansu_socket_close(sock);
    return ifp;

out:
    tansu_socket_close(sock);
    freeifaddr(ifp);
    return NULL;
}

int getifaddrs(struct ifaddrs **ifap)
{
    struct if_nameindex *names;
    int i;
    struct ifaddrs *tail = NULL;
    struct ifaddrs *ifp;

    if (ifap == NULL)
        return -1;

    if ((names = if_nameindex()) == NULL) {
        TLOG_ERROR(TLOG_NWIF, "if_nameindex() failed %d\n", errno);
        return -1;
    }

    for (i = 0; names[i].if_index > 0 && names[i].if_name; i++) {
        if ((ifp = getifaddr(AF_INET, names[i].if_name))) {
            if (tail) {
                tail->ifa_next = ifp;
                tail = ifp;
            } else {
                *ifap = tail = ifp;
            }
        }
        /* TODO: IPv6 */
    }
    if_freenameindex(names);

    return 0;
}

void freeifaddrs(struct ifaddrs *ifp)
{
    struct ifaddrs *tmp;
    while (ifp) {
        tmp = ifp;
        ifp = ifp->ifa_next;
        freeifaddr(tmp);
    }
}
#endif /* HAVE_IFADDRS_H */

#if HAVE_STRSEP
#else /* HAVE_STRSEP */
char *_strsep(char **stringp, const char *delim)
{
    int i;
    int len;
    char *d;
    char *tok;

    if (stringp == NULL || *stringp == NULL || delim == NULL)
        return NULL;

    len = strlen(delim);
    tok = d = *stringp;

    while (*d != '\0') {
        for (i = 0; i < len; i++) {
            if (*d == delim[i]) {
                *d = '\0';
                *stringp = d + 1;
                return tok;
            }
        }
        d++;
    }

    *stringp = NULL;
    return tok;
}
#endif /* HAVE_STRSEP */

